package com.q3d.demo.svc.gateway.filter;

import com.alibaba.fastjson2.JSONObject;
import com.q3d.demo.common.lib.ip.IpUtilForServerHttpRequest;
import com.q3d.demo.common.lib.logger.runner.LogRunner;

import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

@Slf4j /** Lombok 的 Logger 注解 */
@Component /** 注入组件 */
/**
 * 网关的请求日志拦截器
 * 
 * @author 叶湘 ( weixin:yexiang841, email:yexiang841@qq.com )
 */
public class GatewayLogFilter implements GlobalFilter, Ordered {

    @Value(value = "${spring.application.name}") /** 读取配置文件（或远程配置）数据 */
    public String applicationName;

    @Value(value = "${server.port}") /** 读取配置文件（或远程配置）数据 */
    public String serverPort;

    /**
     * 离 0 越远越早执行，必须 < -1，否则不会执行获取后端响应的逻辑
     * 
     * @return int
     */
    @Override
    public int getOrder() {
        return -20;
    }

    @SuppressWarnings(value = "unused")
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 请求开始执行时间
        long startTime = System.currentTimeMillis();
        // 只有当 LogRunner 启动才会做日志记录
        // 请求时间写入线程缓存
        LogRunner.threadLocalTime.set(Long.valueOf(startTime));
        // 将服务名+端口写入线程缓存
        LogRunner.threadLocalName.set(applicationName + "-" + serverPort);
        // 获取请求结构体
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        // 获取来源代理 IP 地址
        String addr = serverHttpRequest.getRemoteAddress().getAddress().getHostAddress();
        // 获取来源真实 IP 地址
        String ipv4 = IpUtilForServerHttpRequest.getRemoteIPv4(serverHttpRequest);
        // 获取请求类型
        String method = serverHttpRequest.getMethodValue().toUpperCase();
        // 获取请求路径
        String uri = serverHttpRequest.getPath().pathWithinApplication().value();
        // 获取路由
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        // 从路由获取目标服务
        String to = route.getId().replace("ReactiveCompositeDiscoveryClient_", "").replace("route-", "");
        // 日志构造器
        StringBuilder requestDataBuilder = new StringBuilder();
        MultiValueMap<String, String> queryParams = serverHttpRequest.getQueryParams();
        /**
         * Gateway 中的 ServerHttpRequest 跟 Controller 中的 HttpServletRequest 稍微不一样：
         * 可以取到 QueryParam，但无法识别 Path 中的参数（因为没有经过 Controller 做参数标记)
         * 只能从 URI 中肉眼识别参数
         * 同时也不包含 POST 请求中的 RequestBody，需要特殊处理
         **/
        requestDataBuilder.append("param=").append(JSONObject.toJSONString(queryParams));
        if (HttpMethod.POST.toString().equals(method)) {
            // 如果是 POST 请求则从之前存下的缓存中读取 Body 参数
            requestDataBuilder.append(",body=").append(exchange.getAttributeOrDefault("cachedRequestBody", ""));
        }
        try {
            ServerHttpResponse serverHttpResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            String responseDataJson = new String(content, Charset.forName("UTF-8"));
                            // 请求时间写入线程缓存
                            LogRunner.threadLocalTime.set(Long.valueOf(startTime));
                            // 将服务名+端口写入线程缓存
                            LogRunner.threadLocalName.set(applicationName + "-" + serverPort);
                            // 统一日志前缀
                            String logRequestBase = "ip: " + ipv4
                                    + " method: " + method
                                    + " uri: " + uri
                                    + " to: " + to;
                            String logRequestData = "request: " + requestDataBuilder.toString();
                            String logResponseData = "response: " + responseDataJson;
                            String springDocPattern = ".*/v3/api-docs";
                            if (uri.matches(springDocPattern)) {
                                // 如果是 SpringDoc 的请求则降级打印日志
                                log.debug("{} {} {}", logRequestBase, logRequestData, logResponseData);
                            } else {
                                log.info("{} {} {}", logRequestBase, logRequestData, logResponseData);
                            }
                            // 发布关键 Tag 到 Skywalking
                            ActiveSpan.tag("ip", ipv4);
                            ActiveSpan.tag("to", to);
                            ActiveSpan.info(logRequestData + " " + logResponseData);
                            byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                            return bufferFactory.wrap(uppedContent);
                        }));
                    }
                    return super.writeWith(body);
                }
            };
            // 继续调用 filter
            return chain.filter(exchange.mutate().response(decoratedResponse).build());
        } catch (Exception e) {
            // 请求过程异常
            String logRequestBase = "ip: " + ipv4
                    + " method: " + method
                    + " uri: " + uri
                    + " to: " + to;
            String logRequestData = "request: " + requestDataBuilder.toString();
            log.error("{} {}", logRequestBase, logRequestData);
            // 发布关键 Tag 到 Skywalking
            ActiveSpan.tag("ip", ipv4);
            ActiveSpan.tag("to", to);
            ActiveSpan.error(logRequestData);
        }
        return chain.filter(exchange);
    }

}
